#include <types.h>
#include <config.h>
#include <string.h>
#include <stdio.h>

#define CTL_CH(c)		((c) - 'a' + 1)
#define CTL_BACKSPACE   ('\b')
#define DEL				((char)255)
#define DEL7			((char)127)

#define HIST_MAX        32
#define HIST_SIZE       CONFIG_SYS_CBSIZE

#define beep()			putc('\a')

#define BEGINNING_OF_LINE() {   \
	while (num) {               \
		putc(CTL_BACKSPACE);    \
		num--;					\
	}							\
}

#define ERASE_TO_EOL() {							\
	if (num < eol_num) {							\
		int i;										\
		for (i = 0; i < eol_num - num; i++)			\
		putc(' ');									\
		do {										\
			putc(CTL_BACKSPACE);					\
		} while (--eol_num > num);					\
	}												\
}               

#define REFRESH_TO_EOL() {          \
	if (num < eol_num) {            \
		wlen = eol_num - num;       \
		putnstr(buf + num, wlen);   \
		num = eol_num;				\
	}								\
}           

static int hist_head;
static int hist_tail;
static int hist_total;

static char* hist_list[HIST_MAX];
static char  hist_lines[HIST_MAX][HIST_SIZE];

char  console_buffer[CONFIG_SYS_CBSIZE];	

/*
 * history list
 */

static void hist_init(void)
{
	int i;

	hist_head = 0;
	hist_tail = 0;
	hist_total = 0;

	for (i = 0; i < HIST_MAX; i++) {
		hist_list[i] = hist_lines[i];
		hist_list[i][0] = '\0';
	}
}

static void hist_add(const char *line)
{
	strcpy(hist_list[hist_tail], line);

	hist_tail = (hist_tail + 1) % HIST_MAX;
	if (hist_tail == hist_head) {
		hist_head = (hist_head + 1) % HIST_MAX;
	} else {
		hist_total++;
	}
}

static char* hist_prev(int n)
{
	int i;

	if (hist_tail + HIST_MAX < n)
		return NULL;

	if (hist_tail < n) 
		i = hist_tail + HIST_MAX - n;
	else 
		i = hist_tail - n;

	return hist_list[i];
}

/*
 * readline
 */

static void putnstr(const char *str, int n)
{
	while (n && (*str)) {
		putc(*str);
		str++;
		n--;
	}
}

static void add_char(char ichar, int insert, int *num, int *eol_num, char *buf, int len)
{
	int wlen;

	if (insert || (*num == *eol_num)) {
		if (*eol_num >= len - 1) {
			beep();
			return;
		}
		(*eol_num)++;
	}

	if (insert) {
		wlen = *eol_num - *num;
		if (wlen > 1) {
			memmove(&buf[*num + 1], &buf[*num], wlen - 1);
		}

		buf[*num] = ichar;
		putnstr(buf + *num, wlen);
		(*num)++;
		while (--wlen) {
			putc(CTL_BACKSPACE);
		}
	} else {
		/* echo the character */
		wlen = 1;
		buf[*num] = ichar;
		putnstr(buf + *num, wlen);
		(*num)++;
	}
}


static void add_str(char *str, int strsize, int insert, int *num, int *eol_num, char *buf, int len)
{
	while (strsize--) {
		add_char(*str, insert, num, eol_num, buf, len);
		str++;
	}
}

int readline(const char *prompt)
{
	static int hist_initted = 0;
	int esc_len = 0;
	char esc_save[8];
	int insert = 1;
	int num = 0;
	int eol_num = 0;
	int wlen;
	char ichar;
	int hist_cur = 0;
	char *buf = console_buffer;
	int len = sizeof(console_buffer);

	buf[0] = '\0';

	if (!hist_initted) {
		hist_init();
		hist_initted = 1;
	}

	if (prompt) 
		puts(prompt);

	while (1) {
		ichar = getchar();

		if ((ichar == '\n') || (ichar == '\r')) {
			puts("\r\n");
			break;
		}

		/*
		 * handle standard linux xterm esc sequences for arrow key, etc.
		 */
		if (esc_len != 0) {
			if (esc_len == 1) {
				if (ichar == '[') {
					esc_save[esc_len] = ichar;
					esc_len = 2;
				} else {
					add_str(esc_save, esc_len, insert, &num, &eol_num, buf, len);
					esc_len = 0;
				}
				continue;
			}

			switch (ichar) {
				case 'D':   /* <- key */
					ichar = CTL_CH('b');
					esc_len = 0;
					break;
				case 'C':   /* -> key */
					ichar = CTL_CH('f');
					esc_len = 0;
					break;  /* pass off to ^F handler */
				case 'H':   /* Home key */
					ichar = CTL_CH('a');
					esc_len = 0;
					break;  /* pass off to ^A handler */
				case 'K':
					ichar = CTL_CH('e');
					esc_len = 0;
					break;
				case 'A':   /* up arrow */
					ichar = CTL_CH('p');
					esc_len = 0;
					break;  /* pass off to ^P handler */
				case 'B':   /* down arrow */
					ichar = CTL_CH('n');
					esc_len = 0;
					break;  /* pass off to ^N handler */
				default:
					esc_save[esc_len++] = ichar;
					add_str(esc_save, esc_len, insert, &num, &eol_num, buf, len);
					esc_len = 0;
					continue;
			}
		}

		switch (ichar) {
			case 0x1b:
				if (esc_len == 0) {
					esc_save[esc_len] = ichar;
					esc_len = 1;
				} else {
					esc_len = 0;
				}
				break;
			case CTL_CH('a'):
				BEGINNING_OF_LINE();
				break;
			case CTL_CH('c'):   /* ^C - break */
				*buf = '\0';    /* discard input */
				return -1;
			case CTL_CH('f'):
				if (num < eol_num) {
					putc(buf[num]);
					num++;
				} else {
					beep();
				}
				break;
			case CTL_CH('b'):
				if (num) {
					putc(CTL_BACKSPACE);
					num--;
				} else {
					beep();
				}
				break;
			case DEL:
			case DEL7:
			case CTL_CH('d'):
				if (num < eol_num) {
					wlen = eol_num - num - 1;
					if (wlen) {
						memmove(&buf[num], &buf[num + 1], wlen);
						putnstr(buf + num, wlen);
					}

					putc(' ');
					do {
						putc(CTL_BACKSPACE);
					} while (wlen--);
					eol_num--;
				} else {
					beep();
				}
				break;
			case CTL_CH('k'):
				ERASE_TO_EOL();
				break;
			case CTL_CH('e'):
				REFRESH_TO_EOL();
				break;
			case CTL_CH('o'):
				insert = !insert;
				break;
			case CTL_CH('x'):
			case CTL_CH('u'):
				BEGINNING_OF_LINE();
				ERASE_TO_EOL();
				break;
			case 8:
				if (num) {
					wlen = eol_num - num;
					num--;
					memmove(&buf[num], &buf[num + 1], wlen);
					putc(CTL_BACKSPACE);
					putnstr(buf + num, wlen);
					putc(' ');
					do {
						putc(CTL_BACKSPACE);
					} while (wlen--);
					eol_num--;
				} else {
					beep();
				}
				break;
			case CTL_CH('p'):
			case CTL_CH('n'):
				{
					char * hline = NULL;

					esc_len = 0;
	
					if (ichar == CTL_CH('p')) {
						if (hist_cur < hist_total) {
							hist_cur++;
							hline = hist_prev(hist_cur);
						}
					} else {
						if (hist_cur > 0) {
							hist_cur--;
							hline = hist_prev(hist_cur);
						}
					}
					if (!hline) {
						beep();
						break;
					}

					/* first, go home */
					BEGINNING_OF_LINE();

					/* erase to end of line */
					ERASE_TO_EOL();

					/* copy new line into place and display */
					strcpy(buf, hline);
					eol_num = strlen(buf);
					REFRESH_TO_EOL();
				}
				break;
			default:
				add_char(ichar, insert, &num, &eol_num, buf, len);
				break;
		}
	}
	buf[eol_num] = '\0';

	if (buf[0]) {
		hist_add(buf);
	}

	return eol_num;	
}


